﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using LINQPad.Extensibility.DataContext;
using LINQPad.OpenAccess.Data;
using LINQPad.OpenAccess.Helpers;
using LINQPad.OpenAccess.Settings;
using Telerik.OpenAccess;
using Telerik.OpenAccess.Config;
using Telerik.OpenAccess.Metadata;

namespace LINQPad.OpenAccess.Services
{
    /// <summary>
    /// <see cref="IBackendService"/>
    /// </summary>
    public class BackendService : IBackendService
    {
        #region Private

        #region - Vars

        private readonly Lazy<ILookup<Backend, BackendProviderMapping>> _backendProviderMap;

        #endregion

        #endregion

        #region Protected

        #region - Functions

        /// <summary>
        /// Creates the default configuration for the specified mapping.
        /// </summary>
        /// <param name="mapping">The mapping.</param>
        /// <returns>
        /// The backend configuration.
        /// </returns>
        protected virtual BackendConfiguration CreateDefaultBackendConfiguration( BackendProviderMapping mapping )
        {
            Guard.ArgumentNotNull( mapping, "mapping" );

            var configuration = new BackendConfiguration( );
            configuration.Backend = mapping.BackendConfigName;
            configuration.ProviderName = mapping.ProviderName;
            return configuration;
        }
        /// <summary>
        /// Gets the backend-provider mappings.
        /// </summary>
        /// <returns>The mappings.</returns>
        protected virtual IEnumerable<BackendProviderMapping> GetBackendProviderMappings( )
        {
            yield return new BackendProviderMapping( Backend.Ads, ConfigDefs.backend_ads, ConfigDefs.AdvantageClient );
            yield return new BackendProviderMapping( Backend.Firebird, ConfigDefs.backend_firebird, ConfigDefs.FirebirdClient, isDefault: true );
            yield return new BackendProviderMapping( Backend.MsSql, ConfigDefs.backend_mssql, ConfigDefs.SqlClient, isDefault: true );
            yield return new BackendProviderMapping( Backend.MySql, ConfigDefs.backend_mysql, ConfigDefs.MySqlClient, isDefault: true );
            yield return new BackendProviderMapping( Backend.Oracle, ConfigDefs.backend_oracle, ConfigDefs.OracleClient, isDefault: true );
            //yield return new BackendProviderMapping( Backend.Oracle, ConfigDefs.backend_oracle, ConfigDefs.ManagedOracleClient );
            yield return new BackendProviderMapping( Backend.PostgreSql, ConfigDefs.backend_postgresql, ConfigDefs.PostgreSqlClient, isDefault: true );
            yield return new BackendProviderMapping( Backend.SqlAnywhere, ConfigDefs.backend_sqlanywhere, ConfigDefs.SqlAnywhereClient, isDefault: true );
            yield return new BackendProviderMapping( Backend.SqlCe, ConfigDefs.backend_sqlce, ConfigDefs.SqlCeClient );
            yield return new BackendProviderMapping( Backend.SqlCe, ConfigDefs.backend_sqlce, ConfigDefs.SqlCeClientSystem );
            yield return new BackendProviderMapping( Backend.SqlCe, ConfigDefs.backend_sqlce, ConfigDefs.SqlCeClient4 );
            yield return new BackendProviderMapping( Backend.SqlCe, ConfigDefs.backend_sqlce, ConfigDefs.SqlCeClient4System, isDefault: true );
            yield return new BackendProviderMapping( Backend.SQLite, ConfigDefs.backend_sqlite, ConfigDefs.SQLiteClient, isDefault: true );
            yield return new BackendProviderMapping( Backend.VistaDb, ConfigDefs.backend_vistadb, ConfigDefs.VistaDBClient );
        }

        #endregion

        #region - Properties

        /// <summary>
        /// Gets the backend provider map.
        /// </summary>
        /// <value>
        /// The backend provider map.
        /// </value>
        protected ILookup<Backend, BackendProviderMapping> BackendProviderMap
        {
            get
            {
                return this._backendProviderMap.Value;
            }
        }

        #endregion

        #endregion

        #region Public

        #region - Constructors

        /// <summary>
        /// Initializes a new instance of the <see cref="BackendService" /> class.
        /// </summary>
        public BackendService( )
        {
            this._backendProviderMap = new Lazy<ILookup<Backend, BackendProviderMapping>>
                                                ( ( ) => this.GetBackendProviderMappings( ).ToLookup( e => e.Backend ) );
        }

        #endregion

        #endregion
        
        #region IBackendService Members

        /// <summary>
        /// Creates the default configuration for the specified backend.
        /// </summary>
        /// <param name="backend">The backend.</param>
        /// <param name="providerName">Name of the provider.</param>
        /// <returns>
        /// The default configuration.
        /// </returns>
        public BackendConfiguration CreateDefaultConfigurationForBackend( Backend backend, string providerName = null )
        {
            var mappings = this.BackendProviderMap[backend];
            if ( !mappings.Any( ) )
                throw new NotSupportedException( String.Format( "The backend '{0}' is not supported !", backend ) );

            BackendProviderMapping mapping;
            if ( String.IsNullOrWhiteSpace( providerName ) )
            {
                var defaultMapping = mappings.FirstOrDefault( e => e.IsDefault );
                if ( defaultMapping == null && mappings.Count( ) > 1 )
                    throw new AmbiguousMatchException
                            (
                                String.Format
                                        (
                                            "No default mapping for backend '{0}' configured and multiple matches found: {1}"
                                            , backend
                                            , String.Join( "\n -", mappings.Select( m => m.ProviderName ) )
                                        )
                            );
                mapping = mappings.Single( );
            }
            else if ( null == ( mapping = mappings.FirstOrDefault( e => e.ProviderName == providerName ) ) )
                throw new InvalidOperationException( String.Format( "The provider '{0}' is not registered for backend '{1}' !", providerName, backend ) );

            return this.CreateDefaultBackendConfiguration( mapping );
        }
        /// <summary>
        /// Creates the default configuration for the specified provider.
        /// </summary>
        /// <param name="providerName">Name of the provider.</param>
        /// <returns>
        /// The default configuration.
        /// </returns>
        /// <exception cref="System.InvalidOperationException">If no backend is registered for <paramref name="providerName" />.</exception>
        public BackendConfiguration CreateDefaultConfigurationForProvider( string providerName )
        {
            Guard.ArgumentNotNullOrEmpty( providerName, "providerName" );

            var mapping = this.BackendProviderMap
                              .SelectMany( m => m )
                              .FirstOrDefault( m => providerName.Equals( m.ProviderName ) );
            if ( mapping == null )
                throw new InvalidOperationException( String.Format( "No backend registered for provider '{0}' !", providerName ) );

            return this.CreateDefaultBackendConfiguration( mapping );
        }
        /// <summary>
        /// Gets the backend configuration.
        /// </summary>
        /// <param name="backendSettings">The backend settings.</param>
        /// <param name="databaseInfo">The database info.</param>
        /// <returns>
        /// The backend configuration.
        /// </returns>
        public BackendConfiguration GetBackendConfiguration( BackendSettings backendSettings, IDatabaseInfo databaseInfo )
        {
            Guard.ArgumentNotNull( backendSettings, "backendSettings" );
            Guard.ArgumentNotNull( backendSettings.Backend, "backendSettings.Backend" );
            Guard.ArgumentNotNull( databaseInfo, "databaseInfo" );

            return this.CreateDefaultConfigurationForBackend( backendSettings.Backend.Value, databaseInfo.Provider );
        }
        /// <summary>
        /// Gets the backend - provider map.
        /// </summary>
        /// <returns>
        /// The backend provider map.
        /// </returns>
        public ILookup<Backend, BackendProviderMapping> GetBackendProviderMap( )
        {
            return this.BackendProviderMap;
        }
        /// <summary>
        /// Gets the provider mappings for the specified backend.
        /// </summary>
        /// <param name="backend">The backend.</param>
        /// <returns>
        /// The provider mappings.
        /// </returns>
        public BackendProviderMapping[] GetProviderMappingsForBackend( Backend backend )
        {
            return this.BackendProviderMap[backend]
                       .ToArray( );
        }
        /// <summary>
        /// Gets the supported backends.
        /// </summary>
        /// <returns>
        /// The supported backends.
        /// </returns>
        public Backend[] GetSupportedBackends( )
        {
            return this.BackendProviderMap
                       .Select( c => c.Key )
                       .ToArray( );
        }
        /// <summary>
        /// Determines whether the specified backend is supported.
        /// </summary>
        /// <param name="backend">The backend.</param>
        /// <returns>
        ///   <c>true</c> if the backend is supported; otherwise, <c>false</c>.
        /// </returns>
        public bool IsBackendSupported( Backend backend )
        {
            return this.BackendProviderMap.Contains( backend );
        }
        /// <summary>
        /// Tries to get the backend from it's configuration name.
        /// </summary>
        /// <param name="backendConfigName">Configuration name of the backend.</param>
        /// <param name="backend">The backend.</param>
        /// <returns>
        ///   <c>true</c> on success; otherwise, <c>false</c>.
        /// </returns>
        public bool TryGetBackendFromConfigurationName( string backendConfigName, out Backend backend )
        {
            backend = Backend.MsSql;

            if ( !String.IsNullOrWhiteSpace( backendConfigName ) )
            {
                var mapping = this.BackendProviderMap
                                  .SelectMany( m => m )
                                  .Where( m => backendConfigName.Equals( m.BackendConfigName ) )
                                  .FirstOrDefault( );
                if ( mapping != null )
                {
                    backend = mapping.Backend;
                    return true;
                }
                if ( Enum.TryParse<Backend>( backendConfigName, true, out backend ) ) // needed to support possibly not yet mapped default backend configurations as best as we can
                    return true;

            }
            return false;
        }
        /// <summary>
        /// Tries to get the default provider for the specified backend.
        /// </summary>
        /// <param name="backend">The backend.</param>
        /// <param name="defaultProviderName">Default name of the provider.</param>
        /// <returns>
        ///   <c>true</c> on success; otherwise, <c>false</c>.
        /// </returns>
        public bool TryGetDefaultProviderForBackend( Backend backend, out string defaultProviderName )
        {
            defaultProviderName = null;

            var mappings = this.BackendProviderMap[backend].ToArray( );
            if ( mappings.Length == 1 )
                defaultProviderName = mappings[0].ProviderName;
            else if ( mappings.Length > 1 )
                defaultProviderName = mappings.Where( m => m.IsDefault ).Select( m => m.ProviderName ).FirstOrDefault( );

            return !String.IsNullOrWhiteSpace( defaultProviderName );
        }

        #endregion
    }
}
